// Package util util/format.go 提供通用的数据格式化工具
package util

import (
	"encoding/hex" // 用于处理 BINARY/VARBINARY/BLOB 等二进制数据，转换为十六进制字符串
	"errors"
	"fmt"     // 用于格式化字符串和错误
	"strconv" // 用于字符串和数字转换
	"strings" // 用于字符串处理
	"time"    // 用于处理时间类型

	log "github.com/sirupsen/logrus" // 使用别名导入日志库
)

// NULL_PLACEHOLDER 定义用于表示 NULL 值的特殊字符串，用于 CRC 计算。
const NULL_PLACEHOLDER = "__CRC_NULL__"

// NULL_KEY_PART 定义用于表示 NULL 键部分的特殊字符串，用于 KeyString 存储。
const NULL_KEY_PART = "__NULL_KEY_PART__"

// KEY_PART_SEPARATOR 定义用于分隔复合键部分的字符串，用于 KeyString 存储。
const KEY_PART_SEPARATOR = ","

// FormatForCRC 将 Go 值格式化为用于计算 CRC32 的字节切片。
// 目标是为相同逻辑值生成一致的字节表示，无论其原始 Go 类型如何。
// 特别注意处理 NULL、数字精度、日期时间格式和二进制数据。
//
// 参数:
//   - value: 从数据库读取并由 GetScannedValue 转换后的 Go 值 (interface{})。
//
// 返回值:
//   - []byte: 用于 CRC32 计算的字节表示。
//
// 注意：此函数返回的字节切片是用于 CRC 计算的“标准化”表示，不一定与数据库中的原始字节相同。
// 这种标准化是确保校验结果在源端和目标端一致的关键。
func FormatForCRC(value interface{}) []byte {
	if value == nil {
		// 为 Go 的 nil 值（对应数据库 NULL）返回固定的字节表示
		return []byte(NULL_PLACEHOLDER)
	}

	// 根据值的实际 Go 类型进行格式化
	switch v := value.(type) {
	case []byte:
		// 二进制数据：转换为十六进制字符串。
		// 这是为了确保二进制数据在不同系统或编码下能产生一致的字符串表示，
		// 从而生成一致的 CRC。原始二进制字节可能受字符集、截断等影响。
		// 使用大写十六进制字符串的字节表示。
		return []byte(hex.EncodeToString(v))
	case string:
		// 字符串：直接转换为字节切片。
		return []byte(v)
	// --- 数字类型 (统一格式化为字符串) ---
	// 使用 strconv 包进行格式化，确保标准输出
	case int:
		return []byte(strconv.FormatInt(int64(v), 10))
	case int8:
		return []byte(strconv.FormatInt(int64(v), 10))
	case int16:
		return []byte(strconv.FormatInt(int64(v), 10))
	case int32:
		return []byte(strconv.FormatInt(int64(v), 10))
	case int64:
		return []byte(strconv.FormatInt(v, 10))
	case uint:
		return []byte(strconv.FormatUint(uint64(v), 10))
	case uint8:
		return []byte(strconv.FormatUint(uint64(v), 10))
	case uint16:
		return []byte(strconv.FormatUint(uint64(v), 10))
	case uint32:
		return []byte(strconv.FormatUint(uint64(v), 10))
	case uint64:
		return []byte(strconv.FormatUint(v, 10))

	case float32:
		// 使用 'g' 格式，它会选择最紧凑的表示（定点或科学计数法），-1 表示尽可能保持精度。
		// 浮点数比较本身有精度风险，但在可接受范围内。
		return []byte(strconv.FormatFloat(float64(v), 'g', -1, 32))
	case float64:
		return []byte(strconv.FormatFloat(v, 'g', -1, 64))

	// --- 布尔类型 ---
	// 统一表示为 "1" (true) 或 "0" (false)。
	case bool:
		if v {
			return []byte("1")
		}
		return []byte("0")

	// --- 时间类型 ---
	case time.Time:
		// 使用 UTC 时区和 RFC3339Nano 格式，确保包含纳秒精度且时区统一。
		// 这是确保跨系统时间戳一致性的关键。
		// 如果数据库驱动返回的是本地时间，需要先转为 UTC。
		return []byte(v.UTC().Format(time.RFC3339Nano))

	// --- 其他未知类型 ---
	default:
		// 对于无法识别的类型，记录警告并使用 Go 的默认 "%v" 格式化。
		// 这可能不是最理想的，但作为回退机制。
		log.Warnf("CRC 计算遇到未处理的 Go 类型 %T (值: %v)，使用 fmt.Sprintf(\"%%v\") 转换。", v, v)
		return []byte(fmt.Sprintf("%v", v))
	}
}

// FormatKeyToString 将 Go 的键值列表 (`[]interface{}`) 格式化为 KEY_PART_SEPARATOR 分隔的字符串。
// 用于将从数据库读取或计算得出的键值（例如 MaxKey）转换为可存储的字符串格式。
// Go 的 `nil` 值会被格式化为 `NULL_KEY_PART`。
// 对于 []byte 类型，使用十六进制表示。对于 time.Time 类型，使用 RFC3339Nano 格式化。
//
// 参数:
//   - keyValues: 包含键值的 Go 切片。
//
// 返回值:
//   - string: 格式化后的键字符串。
//   - error: 如果 `keyValues` 为 nil，则返回错误。
func FormatKeyToString(keyValues []interface{}) (string, error) {
	if keyValues == nil {
		// 如果键值列表本身是 nil，这可能表示没有键列。
		// 0列的情况在 GetInitialStartKey 和 ParseKeyFromString 中都返回/处理空字符串。
		// 让我们在此也返回空字符串以保持一致。
		if len(keyValues) == 0 { // 检查长度是否为 0，以区分 nil slice 和 empty slice
			return "", nil // 0 键列，返回空字符串
		}
		// 理论上不应该出现 nil 但长度不为 0 的切片，防御性检查。
		return "", errors.New("键值列表为 nil 但长度不为 0")
	}

	if len(keyValues) == 0 {
		return "", nil // 空切片（0 键列），返回空字符串
	}

	parts := make([]string, len(keyValues))
	for i, val := range keyValues {
		// 格式化单个值
		parts[i] = formatSingleKeyValueForStorage(val)
	}

	// 使用定义的分隔符连接
	return strings.Join(parts, KEY_PART_SEPARATOR), nil
}

// formatSingleKeyValueForStorage 格式化单个 Go 值，用于存储为 KeyString 的一部分。
// 特别处理 nil 和可能包含分隔符的类型。
func formatSingleKeyValueForStorage(value interface{}) string {
	if value == nil {
		return NULL_KEY_PART // 使用常量表示 NULL
	}

	// 根据不同类型进行处理
	switch v := value.(type) {
	case []byte:
		// 二进制数据通常使用十六进制表示，以避免包含逗号等特殊字符
		// 确保 ParseKeyFromString 可以识别并解码。
		// 使用大写十六进制编码。
		return fmt.Sprintf("0x%X", v)
	case time.Time:
		// 使用 ISO 8601 格式 (RFC3339Nano)，它不包含 KEY_PART_SEPARATOR，且包含纳秒和时区信息，适合存储和排序。
		// 确保 ParseKeyFromString 可以将其作为字符串处理。
		// 转换为 UTC 以确保一致性。
		return v.UTC().Format(time.RFC3339Nano)
	case string:
		// 如果字符串本身包含分隔符 KEY_PART_SEPARATOR，存储时会引起歧义。
		// 当前实现不进行转义，这是一个潜在的限制。
		// 警告：如果字符串键包含 KEY_PART_SEPARATOR (",")，将会导致 ParseKeyFromString 错误解析。
		// TODO: 对于可能包含分隔符的字符串，考虑使用 Base64 或其他编码方式。
		if strings.Contains(v, KEY_PART_SEPARATOR) {
			log.Warnf("键值字符串 '%s' 包含分隔符 '%s'，存储和解析时可能导致问题！请考虑使用安全的编码。", v, KEY_PART_SEPARATOR)
			// 目前按原样存储，需要用户避免在字符串键中使用逗号，或者在 ParseKeyFromString 中添加更复杂的解析逻辑
		}
		return v
	// 其他类型使用标准格式化
	default:
		// 对于所有其他基本类型（数字、布尔等），使用 fmt.Sprintf("%v") 转换为字符串。
		// 确保这些类型的字符串表示不包含 KEY_PART_SEPARATOR。
		// 例如，数字不会包含逗号。
		return fmt.Sprintf("%v", v)
	}
}

// GetInitialStartKey 生成用于第一个 chunk 的起始 KeyString。
// 参数:
//   - numKeys: 键的列数。
//
// 返回值:
//   - string: 格式化后的起始键字符串 (例如 "0", "0,0", "0,0,0")。
//     如果 numKeys 为 0，返回空字符串。
func GetInitialStartKey(numKeys int) string {
	if numKeys <= 0 {
		return "" // 没有键列，返回空字符串
	}
	// 使用字符串 "0" 作为初始值，因为在大多数数据类型（数字、字符串、日期时间）的排序中，它都小于实际的数据值。
	// 使用定义的 KEY_PART_SEPARATOR 分隔符。
	zeros := make([]string, numKeys)
	for i := range zeros {
		zeros[i] = "0" // 使用字符串 "0"
	}
	return strings.Join(zeros, KEY_PART_SEPARATOR) // 使用定义的分隔符连接
}

// ParseKeyFromString 从 KEY_PART_SEPARATOR 分隔的字符串解析键值。
// 用于将存储在数据库或任务中的 KeyString (例如 "100,abc") 解析回 Go 的值列表 (`[]interface{}`)。
//
// 参数:
//   - keyStr: 键的字符串表示，多列用 KEY_PART_SEPARATOR 分隔。特殊值 `NULL_KEY_PART` 代表 NULL。
//   - numKeys: 预期的键列数。
//
// 返回值:
//   - []interface{}: 解析后的值列表。NULL 值表示为 Go 的 `nil`。对于十六进制前缀的值，尝试解码为 []byte，否则作为 `string` 类型。
//   - error: 如果 `numKeys` 无效或 `keyStr` 的部分数与 `numKeys` 不匹配，则返回错误。
//
// 注意：此函数尝试将带 "0x" 前缀的部分解析为 `[]byte`。
func ParseKeyFromString(keyStr string, numKeys int) ([]interface{}, error) {
	if numKeys < 0 {
		return nil, fmt.Errorf("无效的预期键列数: %d", numKeys)
	}
	if numKeys == 0 {
		if keyStr == "" {
			// 0 键列，且 keyStr 为空字符串，表示没有键值需要解析，返回一个长度为0的切片。
			return []interface{}{}, nil
		}
		// 0列时，keyStr必须为空，否则是格式错误
		return nil, fmt.Errorf("预期键列数为 0，但 keyStr 不为空: '%s'", keyStr)
	}

	// 对于 numKeys > 0 的情况，空字符串通常表示“从头开始”，
	// 对应的键值应该是 GetInitialStartKey 返回的值解析而来（即多个 "0" 字符串）。
	// 如果 keyStr 为空，但 numKeys > 0，我们应该视其为 GetInitialStartKey("") 的结果，
	// 即解析为 numKeys 个 "0" 字符串。
	if keyStr == "" {
		// 记录一个信息日志，说明对空字符串的解析行为
		log.Debugf("ParseKeyFromString 收到空 keyStr 但预期 %d 列，解析为 %d 个 '%s' 字符串", numKeys, numKeys, "0")
		values := make([]interface{}, numKeys)
		for i := range values {
			values[i] = "0" // 返回字符串 "0"
		}
		return values, nil
	}

	// 使用定义的分隔符分割字符串
	parts := strings.Split(keyStr, KEY_PART_SEPARATOR)

	// 检查解析后的部分数量是否与预期的键列数匹配
	if len(parts) != numKeys {
		log.Warnf("解析键字符串失败: 部分数量 %d 与预期键列数 %d 不匹配 (keyStr='%s', separator='%s')", len(parts), numKeys, keyStr, KEY_PART_SEPARATOR)
		return nil, fmt.Errorf("键字符串 '%s' 部分数 %d 与预期键列数 %d 不匹配 (使用分隔符 '%s')", keyStr, len(parts), numKeys, KEY_PART_SEPARATOR)
	}

	// 创建结果切片
	result := make([]interface{}, numKeys)
	// 遍历解析出的部分
	for i, part := range parts {
		trimmedPart := strings.TrimSpace(part) // 去除前后空格
		// 检查是否是代表 NULL 的特殊字符串
		if trimmedPart == NULL_KEY_PART {
			result[i] = nil // 将其映射为 Go 的 nil
		} else if strings.HasPrefix(trimmedPart, "0x") {
			// 尝试将十六进制字符串解码回字节切片
			if decodedBytes, err := hex.DecodeString(trimmedPart[2:]); err == nil {
				result[i] = decodedBytes
			} else {
				// 解码失败，记录警告，仍作为字符串处理（可能导致后续问题）
				log.Warnf("无法将键值十六进制字符串 '%s' 解码为字节切片: %v", trimmedPart, err)
				result[i] = trimmedPart
			}
		} else {
			// 其他部分作为字符串存储。包括普通的字符串、数字的字符串形式、时间戳的字符串形式等。
			result[i] = trimmedPart
		}
	}
	return result, nil
}
